/******************************************************************************/
package jamie.org.sil.www.usxUtils;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;


/******************************************************************************/
/**
 * A log manager.
 *
 * @author ARA "Jamie" Jamieson
**/
   

public class LogManager
{
  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                 Public                                 **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
     Singleton.

     @return Singleton instance.
  */

  public static LogManager instance ()
  {
    if (null == m_Instance) m_Instance = new LogManager();
    return m_Instance;
  }


  /****************************************************************************/
  /* Outputs debug information if the config calls for it. */
  
  public void debug (int level, String s)
  {
    if (-1 == m_DebugLevel) m_DebugLevel = Config.instance().getDebugLevel();
    if (0 != (m_DebugLevel & level)) System.out.println(s);
  }

    
  /****************************************************************************/
  public int getErrorCountForFile () { return m_ErrorsForFile; }
  public int getErrorCountForRun () { return m_ErrorsForRun; }
  public int getWarningCountForFile () { return m_WarningsForFile; }
  public int getWarningCountForRun () { return m_WarningsForFile; }
  public void setPrefix (String s) { m_Prefix = s; }

  
  /****************************************************************************/
  /**
     Writes out details of a given exception as an error message.

     @param e Exception.

     @throws Exception
  */
  
  public void error (Exception e)
  {
    try
    {
      String s = e.getMessage();
      String s1 = "Error: " + addPrefix(s);
      m_InteractiveErrorStream.write(s1);
      if ("null".equals(s) || null == s) m_InteractiveErrorStream.write(getFullExceptionDetails(e, true));
      if (null != m_FileStream) m_FileStream.write(s1);
      ++m_ErrorsForFile;
      ++m_ErrorsForRun;
    }
    catch (Exception ee)
    {
    }
  }

  
  /****************************************************************************/
  /**
     Writes out details of a given exception as an error message.

     @param e Exception.

     @throws Exception
  */
  
  public void error (String s)
  {
    try
    {
      String s1 = "Error: " + addPrefix(s);
      m_InteractiveErrorStream.write(s1);
      if (null != m_FileStream) m_FileStream.write(s1);
      ++m_ErrorsForFile;
      ++m_ErrorsForRun;
    }
    catch (Exception e)
    {
    }
  }

  
  /****************************************************************************/
  /**
     Given an exception, obtain details along with a stack trace.

     @param e Exception.
    
     @param forceOutput Forces the details to be written out even when the
                        configuration parameters suggest otherwise.
  */

  public String getFullExceptionDetails (Exception e, boolean forceOutput)
  {
    int debugLevel = 0;
    StringWriter sw = new StringWriter();
    try { debugLevel = Config.instance().getDebugLevel(); } catch (Exception ee) { debugLevel = 4; }
    if (forceOutput || 0 != (debugLevel & 4))
    {
      PrintWriter p = new PrintWriter(sw);
      e.printStackTrace(p);
    }

    String s = e.toString();
    return s  + System.getProperty("line.separator") + sw;
  }

  
  /****************************************************************************/
  /**
     Writes out a given string as an information message.

     @param e Exception.
  */
  
  public void information (Exception e)
  {
    information(addPrefix(e.getMessage()));
  }

  
  /****************************************************************************/
  /**
     Writes out a given string as an information message.

     @param s String.

     @throws Exception
  */
  
  public void information (String s)
  {
    String s1 = addPrefix(s);
    m_InteractiveInformationStream.write(s1);
  }

  
  /****************************************************************************/
  /**
     Reports message counts at the end of a file.
  */

  public void reportEndOfFileCounts ()
  {
    String s = m_ErrorsForFile + " error" + (1 == m_ErrorsForFile ? "" : "s");
    if (Config.instance().getReportWarnings()) s += ", " + m_WarningsForFile + " warning" + (1 == m_WarningsForFile ? "" : "s");
    s += ".";
    information(s);
  }


  /****************************************************************************/
  /**
     Reports message counts at the end of a run.
  */

  public void reportEndOfRunCounts ()
  {
    String s = m_ErrorsForRun + " error" + (1 == m_ErrorsForRun ? "" : "s");
    if (Config.instance().getReportWarnings()) s += ", " + m_WarningsForRun + " warning" + (1 == m_WarningsForRun ? "" : "s");
    s += ".";
    information(s);
  }


  /****************************************************************************/
  /**
     Reports status information at the end of a run.
  */

  public void reportEndOfRunStatus ()
  {
    if (m_ErrorsForRun > 0)
      information("Processing completed with errors");
    else if (m_WarningsForRun > 0)
      information("Processing completed with warnings");
    else
      information("Processing completed successfully");
  }


  /****************************************************************************/
  /**
     Resets error and warning counts for the current file.
  */

  public void resetCountsForFile ()
  {
    m_ErrorsForFile = 0;
    m_WarningsForFile = 0;
  }


  /****************************************************************************/
  /**
     Sets the file path for logging, without affecting anything else.  Closes
     any existing log file.

     @param logFilePath

     @throws IOException
  */

  public void setLogFile (String logFilePath) throws IOException
  {
    if (null != m_FileStream) m_FileStream.close();
    m_FileStream = new LogStreamFile(logFilePath);
  }


  /****************************************************************************/
  /**
   * Indicate whether or not we are to report warnings.  Initially, whether we
   * do or not depends upon the configured setting.  Each time you call the
   * current method with -1, we decrement a count, and each time you call
   * it with +1, we increment it.  If the count is greater than zero, we
   * report warnings regardless of the setting in the configuration information.
   * If it is less than zero, we don't report warnings, no matter what.  If it
   * is zero, we use the configured setting.
   * 
   * If you call the routine with zero, we reset things to work with the
   * configured setting.
   * 
   * @param reportWarnings +1 to report warnings, etc.
   */
  
  public void setReportWarnings (int reportWarnings)
  {
    if (reportWarnings > 0)
      ++m_ReportWarningsCount;
    else if (reportWarnings < 0)
      --m_ReportWarningsCount;
    else
      m_ReportWarningsCount = 0;
  }
  
  
  /****************************************************************************/
  /**
     Sets things up ready to start logging.  Works as though you had called
     the other version of startLogging, with the interactive streams all null.

     @param logFilePath Path to which error and warning messages are sent.

     @throws Exception
  */

  public void startLogging (String logFilePath) throws IOException
  {
    startLogging(null, null, null, logFilePath);
  }

  
  /****************************************************************************/
  /**
     Sets things up.

     @param interactiveErrorStream Interactive stream to which error details
       are sent (System.err is used before the present routine is called, and
       also if this parameter is null.)  Note that the various interactive
       streams may share a common destination if that is what you want.

     @param interactiveWarningStream Ditto, warning messages.

     @param interactiveInformationStream Ditto, information messages.

     @param logFilePath Path to which error and warning messages are sent.
       May be null, in which case no output is logged to file.

     @throws Exception
  */

  public void startLogging (LogStream interactiveErrorStream,
                            LogStream interactiveWarningStream,
                            LogStream interactiveInformationStream,
                            String logFilePath) throws IOException
  {
    m_InteractiveErrorStream = null == interactiveErrorStream ? new LogStreamSystemErr() : interactiveErrorStream;
    m_InteractiveWarningStream = null == interactiveWarningStream ? new LogStreamSystemErr() : interactiveWarningStream;
    m_InteractiveInformationStream = null == interactiveInformationStream ? new LogStreamSystemOut() : interactiveInformationStream;
    m_FileStream = null == logFilePath ? null : new LogStreamFile(logFilePath);

    m_ErrorsForFile = 0;
    m_ErrorsForRun = 0;
    m_WarningsForFile = 0;
    m_WarningsForRun = 0;
  }

  
  /****************************************************************************/
  /**
     Stops logging -- closes all output streams.
  */

  public void stopLogging ()
  {
    m_InteractiveErrorStream.close();
    m_InteractiveWarningStream.close();
    m_InteractiveInformationStream.close();
    if (null != m_FileStream) m_FileStream.close();
  }

  
  /****************************************************************************/
  /**
     Writes out details of a given exception as a warning message.
  */
  
  public void warning (Exception e)
  {
    warning(e.getMessage());
  }

  
  /****************************************************************************/
  /**
     Writes out a given string as a warning message.
  */
  
  public void warning (String s)
  {
    if (0 == m_ReportWarningsCount) if (!Config.instance().getReportWarnings()) return;
    if (m_ReportWarningsCount < 0) return;
    String s1 = "Warning: " + addPrefix(s);
    m_InteractiveWarningStream.write(s1);
    if (null != m_FileStream) m_FileStream.write(s1);
    ++m_WarningsForFile;
    ++m_WarningsForRun;
  }

  



  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                Private                                 **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  public interface LogStream
  {
    abstract public void close ();
    abstract public void write (String s);
  }
  
  
  /****************************************************************************/
  private class LogStreamFile implements LogStream
  {
    LogStreamFile (String filePath) throws IOException
    {
      try { Utils.deleteFile(filePath); } catch(Exception e) {}
      m_Stream = new PrintStream(new FileOutputStream(filePath), true);
    }

    @Override public void close () { try { m_Stream.close(); } catch (Exception e) {} }
    @Override public void write (String s) { try { m_Stream.println(s); } catch (Exception e) {} }
    private PrintStream m_Stream = null;
  }

  
  /****************************************************************************/
  private class LogStreamSystemErr implements LogStream
  {
    @Override public void close () { System.err.flush(); }
    @Override public void write (String s) { System.err.println(s); }
  }

  
  /****************************************************************************/
  private class LogStreamSystemOut implements LogStream
  {
    @Override public void close () { System.out.flush(); }
    @Override public void write (String s) { System.out.println(s); }
  }

  
  /****************************************************************************/
  /* Adds the current prefix to a given string. */
  
  private String addPrefix (String s)
  {
    if (0 != m_Prefix.length()) return m_Prefix + ": " + s;
    
    if (0 != Bookmark.instance().getBookNo())
    {
      String prefix = Bookmark.instance().getUbsAbbreviatedName();
      int x = Bookmark.instance().getChapter();
      if (0 != x) prefix += " " + x;
      x = Bookmark.instance().getVerse();
      if (0 != x) prefix += ":" + x;
      return prefix + ": " + s;
    }
    
    return s;
  }


  /****************************************************************************/
  private int m_DebugLevel = -1;
  private int m_ErrorsForFile = 0;
  private int m_ErrorsForRun = 0;
  private int m_WarningsForFile = 0;
  private int m_WarningsForRun = 0;

  private LogStream m_FileStream = null;
  private LogStream m_InteractiveErrorStream = new LogStreamSystemErr();
  private LogStream m_InteractiveInformationStream = new LogStreamSystemOut();
  private LogStream m_InteractiveWarningStream = new LogStreamSystemErr();
  
  private static LogManager m_Instance = null;

  private String m_Prefix = ""; // Prefix for messages.
  private int m_ReportWarningsCount = 0;
}
